#include <QString>
#include <QPainter>
#include <QApplication>
#include <QMouseEvent>
#include <QDebug>

#ifdef Q_OS_LINUX
#include <unistd.h>
#endif

#include <stdio.h>
#include <math.h>
#include <time.h>
#include <qwebcam.hpp>

#define FOURCC(F, O, U, R) ((R << 24) | (U << 16) | (O << 8) | F)
#define SENDFRAME \
    SIGNAL(sendFrame(unsigned char *,int,int,int,int,unsigned int))
#define GOTFRAME \
    SLOT(gotFrame(unsigned char *,int,int,int,int,unsigned int))
#define CHECK_IMAGE(image) (image.isNull() || image.width() != width || image.height() != height)

QWebcam::QWebcam(QWidget *parent) :
    WIDGET(parent),
    m_TrackingActive(true),
    m_SelectArea(false),
    m_RubberBand(new QRubberBand(QRubberBand::Rectangle, this)),
    m_RGBTraceEnabled(false),
    m_Device(NULL),
    m_ImgData(NULL),
    m_Image(QImage(0, 0)),
    m_RgbLabel(new QLabel),
    m_RBx(-1),
    m_RBy(-1),
    m_Mutex(new QMutex(QMutex::NonRecursive)),
    m_Tracker(NULL)
{
    QFontMetrics    mt(font());
    QSize			size(mt.width("RGB(255, 255, 255)") + 4, mt.height() + 4);

    if (m_RgbLabel) {
        m_RgbLabel->setWindowFlags(Qt::ToolTip);
        m_RgbLabel->hide();
        m_RgbLabel->setAlignment(Qt::AlignCenter);
        m_RgbLabel->resize(size);
        m_RgbLabel->setStyleSheet("QWidget {background-color: white;}");
    }
    m_Selection.setRect(0, 0, -1, -1);
    m_RubberBand->setGeometry(0, 0, -1, -1);
    m_RubberBand->setVisible(false);

    setAttribute(Qt::WA_NoSystemBackground);
    setAttribute(Qt::WA_OpaquePaintEvent);

    setMouseTracking(true);
}

QWebcam::~QWebcam()
{
    if (m_RgbLabel)
        delete m_RgbLabel;

    if (m_Mutex)
        delete m_Mutex;
}

QSize
QWebcam::sizeHint() const
{
    return QSize(640, 480);
}

void
QWebcam::leaveEvent(QEvent *)
{
    if (m_RGBTraceEnabled && m_Device != NULL && m_Device->isConnected())
        m_RgbLabel->hide();
}

void
QWebcam::enterEvent(QEvent *)
{
    if (m_RGBTraceEnabled && m_Device != NULL && m_Device->isConnected())
        m_RgbLabel->show();
}

void
QWebcam::mouseMoveEvent(QMouseEvent *e)
{
    if (m_Device == NULL && !m_Device->isConnected())
        return;

    int x = e->pos().x();
    int y = e->pos().y();
    if (m_RGBTraceEnabled) {
        QPixmap pixmap = QPixmap::grabWidget(this, x, y);
        QImage  image  = pixmap.toImage();

        if (image.isNull())
            return;

        QRgb	rgb(image.pixel(0, 0));

        m_RgbLabel->setText(QString("RGB(%1, %2, %3)")
                            .arg(qRed(rgb))
                            .arg(qGreen(rgb))
                            .arg(qBlue(rgb)));

        m_RgbLabel->move(e->globalPos() + QPoint(10, 10));
    }
    if (m_RBx >= 0 && m_RBy >= 0 && m_SelectArea) {
        QRect  rbBox(m_RBx, m_RBy, x - m_RBx, y - m_RBy);

        m_RubberBand->setGeometry(rbBox.normalized());
    }
}

void
QWebcam::mousePressEvent(QMouseEvent *e)
{
    if (m_SelectArea) {
        m_RubberBand->setVisible(true);
        m_RBx = e->pos().x();
        m_RBy = e->pos().y();
    }
}

void
QWebcam::mouseReleaseEvent(QMouseEvent *e)
{
    if (m_SelectArea) {
        float   mx;
        float   my;
        int     x;
        int     y;
        int     w;
        int     h;

        m_Selection = m_RubberBand->geometry();

        x = m_Selection.left();
        y = m_Selection.top();
        w = m_Selection.width();
        h = m_Selection.height();

        mx = (float)m_Device->nativeWidth() / (float)width();
        my = (float)m_Device->nativeHeight() / (float)height();

        x = floor(mx * (float)x);
        y = floor(my * (float)y);
        w = floor(mx * (float)w);
        h = floor(my * (float)h);

        m_Selection.setRect(x, y, w, h);

        m_RubberBand->setGeometry(0, 0, -1, -1);
        m_RubberBand->setVisible(false);
        m_RBx = -1;
        m_RBy = -1;

        startTrackingSelectedArea();
        emit areaSelected();
    }
    m_SelectArea = false;

    int     x      = e->pos().x();
    int     y      = e->pos().y();
    QPixmap pixmap = QPixmap::grabWidget(this, x, y);
    QImage  image  = pixmap.toImage();

    if (image.isNull())
        return;

    QRgb	rgb(image.pixel(0, 0));

    m_RgbLabel->setText(QString("RGB(%1, %2, %3)")
                        .arg(qRed(rgb))
                        .arg(qGreen(rgb))
                        .arg(qBlue(rgb)));

    emit clicked(QColor(rgb));
}


void
QWebcam::paintEvent(QPaintEvent *)
{
    if (m_Device == NULL || !m_Device->isConnected())
        return;

    QPainter    painter(this);

    if (!m_Image.isNull())
        painter.drawImage(rect(), m_Image);
    else
        painter.fillRect(rect(), palette().background());

    painter.end();
}


void
QWebcam::gotFrame(unsigned char *ptr,
                  int width,
                  int height,
                  int bpl,
                  int fourcc,
                  unsigned int length)
{
    Q_UNUSED(length)

    if (m_Device == NULL || !m_Device->isConnected())
        return;

    switch (fourcc) {
    case FOURCC('M', 'J', 'P', 'G'):
        break;
    case FOURCC('H', '2', '6', '4'):
        break;
    case FOURCC('R', 'G', 'B', '3'):
        if (m_Mutex->tryLock()) {

            if (m_TrackingActive && m_Tracker)
                m_Tracker->track(ptr, width, height, bpl);

            if (CHECK_IMAGE(m_Image))
                m_Image = QImage(ptr, width, height, bpl, QImage::Format_RGB888);

            m_Mutex->unlock();
        }
        break;
    case FOURCC('P', 'J', 'P', 'G'):
        fprintf(stderr, "ERROR: este formato no se puede convertir directamente.\n");
        fprintf(stderr, "\tpor favor reejecute el programa Precargando v4l2convert.so\n");
        fprintf(stderr, "\n\tej. LD_PRELOAD=/usr/lib/libv4l/v4l2convert.so PROGRAMA\n");
        deleteLater();
        exit (2);
        break;
    default:
        m_Image = QImage();
        return;
    }
    QApplication::processEvents();
    update();
}

void
QWebcam::setDevice(char *uid, char *name)
{
    m_CurrentUid  = QString(uid);
    m_CurrentName = QString(name);

    if (m_Device) {
        m_Image = QImage(0, 0);
        disconnect(m_Device, SENDFRAME, this, GOTFRAME);

        delete m_Device;
    }

    m_Device = new CPCapDevice(uid, name, 0, this);
    if (m_Device) {
        m_Device->connectDevice();
        if (m_Device->isConnected())
            connect(m_Device, SENDFRAME, this, GOTFRAME);
    }
}

QWebcamResolutionList
QWebcam::resolutionsList()
{
    if (!m_Device)
        return QWebcamResolutionList();

    QWebcamResolutionList     list;
    const FormatsInfo   *formats;

    formats = m_Device->listFormats();

    if (!formats)
        return QWebcamResolutionList();

    for (int i = 0 ; i < formats->count ; i++) {
        int                 width;
        int                 height;
        const FormatSize    *sizes;
        const char          *description;
        QString             str;

        sizes  = formats[i].sizes;

        for (int j = 0 ; j < formats[i].sizes_count ; j++) {
            width  = sizes[j].width;
            height = sizes[j].height;

            switch (formats[i].fmt) {
            case FOURCC('R', 'G', 'B', '3'):
                description = "(RGB 24 bits)";
                break;
            default:
                description = NULL;
                break;
            }
            if (description) {
                str = QString("%1 (%2x%3)\n")
                      .arg(description)
                      .arg(width)
                      .arg(height);
                QWebcamResolution     current;

                current.name   = description;
                current.fourcc = formats[i].fmt;
                current.index  = i;
                current.width  = width;
                current.height = height;
                current.size   = j;

                list << current;
            }
        }
    }
    return list;
}

QWebcamFrameRateList
QWebcam::framerateList(QWebcamResolution resolution)
{
    if (!m_Device)
        return QWebcamFrameRateList();

    QWebcamFrameRateList    list;
    const FormatsInfo       *formats;

    formats = m_Device->listFormats();

    if (!formats)
        return QWebcamFrameRateList();

    for (int i = 0 ; i < formats->count ; i++) {
        const FormatsInfo &format(formats[resolution.index]);
        const FormatSize  size(format.sizes[resolution.size]);

        switch (format.fmt) {
        case FOURCC('R', 'G', 'B', '3'):
            for (int j = 0 ; j < size.fps_count ; j++) {
                QWebcamFrameRate    fr;

                fr.index = j;
                fr.fps   = size.fps_denominator[j] / size.fps_numerator[j];

                list << fr;
            }
            break;
        default:
            break;
        }
    }
    return list;
}


void
QWebcam::setResolution(QWebcamResolution info)
{
    if (m_CurrentUid.isEmpty() || m_CurrentName.isEmpty())
        return;

    QByteArray  uid(m_CurrentUid.toLatin1());
    QByteArray  name(m_CurrentName.toLatin1());

    if (m_Device) {
        m_Image = QImage(0, 0);
        disconnect(m_Device, SENDFRAME, this, GOTFRAME);

        delete m_Device;
    }

    m_Device = new CPCapDevice(uid.constData(),
                               name.constData(),
                               0,
                               info.width,
                               info.height,
                               info.fourcc,
                               this);
    if (m_Device) {
        m_Device->connectDevice();
        if (m_Device->isConnected())
            connect(m_Device, SENDFRAME, this, GOTFRAME);
    }
}

void
QWebcam::enableRGBTrace(bool enabled)
{
    m_RGBTraceEnabled = enabled;
    m_RgbLabel->hide();
}

void
QWebcam::selectArea(bool select)
{
    m_SelectArea = select;
}

QImage
QWebcam::capturedSelection()
{
    return m_Captured;
}

void
QWebcam::startTrackingSelectedArea()
{
    if (!m_Mutex->tryLock())
        return;

    if (m_ImgData == NULL)
        return;

    int             top;
    int             Width;
    int             width;
    int             height;
    int             left;
    int             stride;
    unsigned char   *data;
    unsigned char   *ptr;

    width  = m_Selection.width();
    height = m_Selection.height();
    Width  = 3 * m_Device->nativeWidth();
    left   = 3 * m_Selection.left();
    top    = m_Selection.top();
    stride = Width - 3 * width;
    data   = m_ImgData + top * Width + left;

    m_Captured = QImage(QSize(width, height), QImage::Format_RGB888);
    m_Captured.fill(0);

    ptr = data;
    for (int y = 0 ; y < height ; y++) {
        for (int x = 0 ; x < width ; x++) {
            unsigned char r;
            unsigned char g;
            unsigned char b;

            r = *(ptr + 3 * x);
            g = *(ptr + 3 * x + 1);
            b = *(ptr + 3 * x + 2);

            m_Captured.setPixel(x, y, qRgb(r, g, b));

            ptr += 3;
        }
        ptr += stride;
    }
    m_Mutex->unlock();
}

void
QWebcam::stopTracking()
{
    emit trackingStoped();

    m_TrackingActive = false;
}

QImage
QWebcam::capture()
{
    return m_Image.copy(0, 0, m_Image.width(), m_Image.height());
}

void
QWebcam::setTracker(TCTracker *tracker)
{
    m_Tracker = tracker;
}
